<?php
class SocuteTemplateSection
{
	public $name = '';
	public $keysValues = null;
	public $sections = null;
	public $template = null;
	
	function __construct($name, $template)
	{
		$this->name = $name;
		$this->template = $template;
	}
	
	public function set($name, $value)
	{
		if($this->keysValues === null){
			$this->keysValues = array();
		}
		
		$this->keysValues[$name] = $value;
	}
	
	public function dispatch($section)
	{
		if($this->sections === null){
			$this->sections = array();
		}
		
		if(!array_key_exists($section->name, $this->sections)){
			$this->sections[$section->name] = array();
		}
		
		$this->sections[$section->name][] = $section;
	}
	
	public function render($text, $writer)
	{
		while(strlen($text) > 0){
			$arrResult = $this->searchNextSection($text);
			
			if(strlen($arrResult['text']) > 0){
				$this->renderText($arrResult['text'], $writer);
			}
			
			if(strlen($arrResult['section']['name']) > 0
				&& $this->sections !== null)
			{
				$sectionName = $arrResult['section']['name'];
				if(array_key_exists($sectionName, $this->sections)){
					foreach($this->sections[$sectionName] as $section){
						$section->render($arrResult['section']['text'], $writer);
					}
				}
			}
			$text = $arrResult['nextText'];
		}
	}
	
	private function renderText($text, $writer)
	{
		// iteration to avoid recursion
		while(strlen($text) > 0){
			$text = $this->parseKeys($text, $writer);
		}
	}
	
	private function parseKeys($text, $writer)
	{
		$arrResult = $this->searchNextKey($text);
		$templateEscapeChar = $this->template->templateEscapeChar;
		$valueOpenTag = $this->template->valueOpenTag;
		$sectionOpenTag = $this->template->sectionOpenTag;
		
		if($arrResult['text'] != ''){
			$writer->write(str_replace(
				$templateEscapeChar.$valueOpenTag, $valueOpenTag,
				str_replace(
					$templateEscapeChar.$sectionOpenTag, $sectionOpenTag,
					$arrResult['text'])));
		}
    
    if($this->keysValues !== null){
  		if(array_key_exists($arrResult['key'], $this->keysValues)){
        $value = $this->keysValues[$arrResult['key']];
        if(is_object($value)
          && get_class($value) == 'SocuteTemplate')
        {
          $value->render();
        }else{
          $writer->write($this->keysValues[$arrResult['key']]);
        }
  		}
    }
		
		return $arrResult['nextText'];
	}
	
	private function searchNextKey($text)
	{
		$arrResult = array();
		$arrResult['text'] = '';
		$arrResult['key'] = '';
		$arrResult['nextText'] = '';
		
		$valueOpenTag = $this->template->valueOpenTag;
		$valueCloseTag = $this->template->valueCloseTag;
		$valueOpenTagLength = strlen($valueOpenTag);
		$valueCloseTagLength = strlen($valueCloseTag);
		
		$startPos = 0;
    $tagOpenPos = $this->searchNextTagToken($text, $valueOpenTag, $startPos);
    if($tagOpenPos === false){
      $arrResult['text'] = $text;
      return $arrResult;
    }

		$arrResult['text'] = substr($text, 0, $tagOpenPos);
		$tagNamePos = $tagOpenPos + strlen($valueOpenTag);
		
		// cari close tag
		$tagClosePos = strpos($text, $valueCloseTag, $tagNamePos);
		if($tagClosePos === false){
			throw new Exception('Missing Key Close Tag');
		}
		
		// validasi nama key
		$keyName = substr($text, $tagNamePos, $tagClosePos - $tagNamePos);
		$this->validateIdentifier($keyName);
		
		$arrResult['key'] = $keyName;
		$arrResult['nextText'] = substr($text, $tagClosePos + $valueCloseTagLength);
		return $arrResult;
	}
	
	private function searchNextSection($text)
	{
		$arrResult = array();
		$arrResult['text'] = '';
		$arrResult['section'] = array('name'=>'', 'text'=>'');
		$arrResult['nextText'] = '';

		$sectionOpenTag = $this->template->sectionOpenTag;
		$sectionStartTag = $this->template->sectionStartTag;
		$sectionCloseTag = $this->template->sectionCloseTag;
		$sectionOpenTagLength = strlen($sectionOpenTag);
		$sectionStartTagLength = strlen($sectionStartTag);
		$sectionCloseTagLength = strlen($sectionCloseTag);
		
		// cari open tag
		$startPos = 0;
    $tagOpenPos = $this->searchNextTagToken($text, $sectionOpenTag, $startPos);
    if($tagOpenPos === false){
      // jadikan semua sisa text sebagai TemplateText
      $arrResult['text'] = $text;
      return $arrResult;
    }

		// jadikan text sampai open tag sebagai TemplateText
		$arrResult['text'] = substr($text, 0, $tagOpenPos);
		$tagNamePos = $tagOpenPos + $sectionOpenTagLength;
		
		// cari start tag
		$tagStartPos = strpos($text, $sectionStartTag, $tagNamePos);
		if($tagStartPos === false){
			throw new Exception('Terdapat section tanpa section start');
		}
		
		// cek apakah nama section valid
		$sectionName = substr($text, $tagNamePos, $tagStartPos - $tagNamePos);
		$this->validateIdentifier($sectionName);
		
		$arrResult['section']['name'] = $sectionName;
		$sectionStartPos = $tagStartPos + $sectionStartTagLength;
		$tagClosePos = $this->parseSectionEnd($text, $sectionStartPos, $sectionName);
		
		$arrResult['section']['text'] = substr($text, $sectionStartPos, $tagClosePos - $sectionStartPos);
		$arrResult['nextText'] = substr($text, $tagClosePos + $sectionCloseTagLength);

		return $arrResult;
	}
	
	private function parseSectionEnd($text, $startPos, $sectionName)
	{
		$templateEscapeChar = $this->template->templateEscapeChar;
		$sectionOpenTag = $this->template->sectionOpenTag;
		$sectionStartTag = $this->template->sectionStartTag;
		$sectionCloseTag = $this->template->sectionCloseTag;
		
		$templateEscapeCharLength = strlen($templateEscapeChar);
		$sectionOpenTagLength = strlen($sectionOpenTag);
		$sectionStartTagLength = strlen($sectionStartTag);
		$sectionCloseTagLength = strlen($sectionCloseTag);
		
		// cari close tag
    $tagClosePos = $this->searchNextTagToken($text, $sectionCloseTag, $startPos);
    if($tagClosePos === false){
      throw new Exception('Missing Section Close Tag For: '.$sectionName.'!');				
    }

		// cari open tag
    $tagOpenPos = $this->searchNextTagToken($text, $sectionOpenTag, $startPos);
    if($tagOpenPos === false){
      // good, we have found the end of section tag
      return $tagClosePos;
    }
		
		// ada open tag di tengah2, yg kita menemukan close tag untuk section di tengah section ini, ulangi lagi pencarian dengan start baru
		if($tagOpenPos < $tagClosePos){
			$tagClosePos = $this->parseSectionEnd($text, $tagClosePos + $sectionCloseTagLength, $sectionName);
		}
		
		return $tagClosePos;
	}
  
  private function searchNextTagToken($text, $tagToken, &$startPos)
  {
    $tagTokenLength = strlen($tagToken);
    $templateEscapeChar = $this->template->templateEscapeChar;
    
		while(true){
			$tagPos = strpos($text, $tagToken, $startPos);
      if($tagPos !== false){
        // cek apakah tag tidak didahului oleh escape char
  			if($tagPos == 0 || $text{$tagPos - 1} != $templateEscapeChar){
  				break;
  			}
  			else{
  				$startPos = $tagPos + $tagTokenLength;
  			}
      }
			else{
				return false;
			}
		}
    return $tagPos;
  }
  
  private function validateIdentifier($text)
  {
    $keys = preg_split("/[\s,]+/", $text);
		if(sizeof($keys) > 1){
			throw new Exception('Invalid Identifier: '.$text);
		}
  }
}
?>